#include "tcp_connection.hpp"

#include <vector>
#include <functional>
#include <iostream>
#include "../base/logger.h"

namespace easyasio {
namespace net {

TcpConnection::TcpConnection(asio::io_service& io_service)
	: socket_(io_service),
	  loop_(io_service),
	  state_(Connecting)
{
}

TcpConnection::~TcpConnection()
{
    LOG_TRACE("TcpConnection destory. {}", (void*)this);
}
asio::ip::tcp::socket& TcpConnection::socket()
{
	return socket_;
}

void TcpConnection::start()
{
    assert(state() == Connecting);
    setState(Connected);

    LOG_TRACE("net::TcpConnection accept, ip {}, port {}. {}", 
        socket_.remote_endpoint().address().to_string(), socket_.remote_endpoint().port(), (void*)this);

    if (connectionCallback_)
    {
        loop_.dispatch(std::bind(connectionCallback_, shared_from_this()));
    }

	socket_.async_read_some(asio::buffer(tmp_recv_buffer_), 
		bind(&TcpConnection::handleReadableInLoop, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}

void TcpConnection::handleReadableInLoop(const asio::error_code& e, std::size_t bytes_transferred)
{
	if (e)
	{
        LOG_TRACE("net::TcpConnection::handleReadableInLoop: {}:{}. {}", e.value(), e.message(), (void*)this);
        forceClose();
		return; 
	}

    if ((state()==Connected || state()==Halfclosing || state()==Halfclosed))
    {
        pending_recv_buffer_.append(tmp_recv_buffer_, bytes_transferred);
    	if (messageCallback_)
    		messageCallback_(shared_from_this(), &pending_recv_buffer_);
    }

    if ((state()==Connected || state()==Halfclosing || state()==Halfclosed))
    {
    	socket_.async_read_some(asio::buffer(tmp_recv_buffer_, RECV_CACHED_SIZE),
    		bind(&TcpConnection::handleReadableInLoop, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
    }
}

void TcpConnection::handleWritableInLoop(const asio::error_code& e)
{
	if (e)
	{
        LOG_TRACE("net::TcpConnection::handleWritableInLoop: {}:{}. {}", e.value(), e.message(), (void*)this);
        forceClose();
        return;
	}

	sending_buffer_.retrieveAll();
    if (state()==Connected || state()==Halfclosing)
    {
    	socketTrySendInloop();
    }
}

void TcpConnection::socketTrySendInloop()
{
    if (pending_send_buffer_.readableBytes()==0)
    {
        //do half close
        if (!isWriting() && state()==Halfclosing) 
        { 
            shutdownInLoop();
        }
		return;
    }

    if (sending_buffer_.readableBytes() > 0)
        return;

	sending_buffer_.swap(pending_send_buffer_);
	
    asio::async_write(socket_,
          asio::buffer(sending_buffer_.peek(), sending_buffer_.readableBytes()),
          std::bind(&TcpConnection::handleWritableInLoop, shared_from_this(), std::placeholders::_1));
}

void TcpConnection::send(const std::string& msg)
{
    if (state() != Connected)
    {
        LOG_DEBUG("net::TcpConnection::send state != Connected, state={}, giving up send. {}", state(), (void*)this);
        return;
    }
	loop_.post(std::bind(&TcpConnection::sendInLoop, shared_from_this(), msg));
}

void TcpConnection::sendInLoop(std::string& msg)
{
    if (state()==Disconnected || state()==Disconnecting)
    {
        LOG_DEBUG("net::TcpConnection::sendInLoop state == Connected, giving up send {}", (void*)this);
        return;
    }

	pending_send_buffer_.append(msg.c_str(), msg.size());
    socketTrySendInloop();
}

void TcpConnection::shutdown()
{
    if (state() == Connected)
    {
        LOG_DEBUG("net::TcpConnection::shutdown. {}", (void*)this);
        setState(Halfclosing);
        loop_.post(std::bind(&TcpConnection::shutdownInLoop, shared_from_this()));
    }
    else
    {
        LOG_DEBUG("net::TcpConnection::shutdown state invalied = {}. {}", state(), (void*)this);
    }
}

void TcpConnection::shutdownInLoop()
{
    if (pending_send_buffer_.readableBytes() == 0 && !isWriting() && state()==Halfclosing)
    {
        setState(Halfclosed);
        LOG_TRACE("net::TcpConnection::shutdownInLoop. {}", (void*)this);
        socket_.shutdown(asio::ip::tcp::socket::socket_base::shutdown_send);
    }
}

void TcpConnection::forceClose()
{
    if (state()==Disconnected || state()==Connecting || state()==Disconnecting)
    {
        LOG_DEBUG("net::TcpConnection::forceClose state invalied = {}. {}", state(), (void*)this);
        return;
    }

    LOG_DEBUG("net::TcpConnection::forceClose. {}", (void*)this);
    setState(Disconnecting);
    loop_.dispatch(std::bind(&TcpConnection::forceCloseInloop, shared_from_this()));
}

void TcpConnection::forceCloseInloop() 
{ 
    assert(state() == Disconnecting);
    handleCloseInLoop();
}

void TcpConnection::handleCloseInLoop()
{
    setState(Disconnected);
    LOG_TRACE("net::TcpConnection close socket. {}", (void*)this);
    socket_.cancel();
    if (closeCallback_)
        closeCallback_(shared_from_this());
}

} // namespace net 
} // namespace easyasio 
